<!--
 * @Author: 蔡亚风
 * @Date: 2022-04-15 15:54:17
 * @LastEditTime: 2022-04-29 16:29:35
 * @LastEditors: 蔡亚风
 * @Description: 录制视频H5插件
 * @FilePath: \prod\src\components\RecorderVideo.vue
-->
<template>
  <div id="recorder">
    <p v-if="loading" class="loading">摄像头加载中...</p>
    <video
      ref="inlineVideo"
      :muted="isMuted"
      playsinline
      webkit-playsinline
      :style="{
        transform:
          facingMode === 'user'
            ? 'translate(-50%,-50%) rotateY(180deg)'
            : 'translate(-50%,-50%) rotateY(0)',
      }"
      preload="none"
    ></video>
    <div
      v-show="maskImg"
      class="maskBox"
      :style="{
        backgroundImage: `url(${maskImg})`,
      }"
    ></div>
  </div>
</template>
<script>
import RecordRTC from "recordrtc";
import { verifyVideo } from "@/utils";
import { fixWebDurationFn } from "@/lib/fix-webm-duration/fix-webm-duration.utils";
const isIos = /(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent);
const sleep = (time = 0) =>
  new Promise((res) => {
    setTimeout(res, time);
  });
export default {
  name: "RecorderVideo",
  props: {
    maskImg: {
      //模态框图
      type: String,
      default: "",
    },
    isMuted: {
      //预览是否静音
      type: Boolean,
      default: true,
    },
    facingMode: {
      //摄像头前后置 前置user 后置environment
      type: String,
      default: "user",
    },
    beforePlayFn: {
      type: Function, //预览视频前调用  promise函数
      default: () => {
        return new Promise((resolve) => resolve());
      },
    },
    playSuccessFn: {
      type: Function, //播放成功后调用 普通函数
      default: () => {},
    },
    playErrorFn: {
      type: Function, //播放失败后调用 普通函数
      default: () => {},
    },
  },
  data() {
    return {
      recorderFrame: null, //录制帧
      currentStream: null, //流媒体
      video1Dom: null, //视屏流dom
      loading: false,
      startRecordTime: "", //开始录制时间
    };
  },
  watch: {
    facingMode() {
      //根据前后置摄像头的变化来重启视频
      this.startCamera();
    },
  },
  mounted() {
    this.$nextTick(() => {
      this.startCamera();
    });
    //初始化
  },
  // eslint-disable-next-line vue/no-deprecated-destroyed-lifecycle
  beforeDestroy() {
    //v2销毁
    console.log("beforeDestroy");
    this.stopMediaTracks();
    this.stopAndblobDownload();
  },
  beforeUnmount() {
    //v3销毁
    console.log("beforeUnmount");
    this.stopMediaTracks();
    this.stopAndblobDownload();
  },
  methods: {
    //开启预览视频
    async startCamera() {
      this.loading = true;
      await this.beforePlayFn();
      // 老的浏览器可能根本没有实现 mediaDevices，所以我们可以先设置一个空的对象
      if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {};
      }
      // 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
      // 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
      if (navigator.mediaDevices.getUserMedia === undefined) {
        navigator.mediaDevices.getUserMedia = function (constraints) {
          // 首先，如果有getUserMedia的话，就获得它
          let getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

          // 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
          if (!getUserMedia) {
            return Promise.reject(
              new Error("getUserMedia is not implemented in this browser")
            );
          }
          // 否则，为老的navigator.getUserMedia方法包裹一个Promise
          return new Promise(function (resolve, reject) {
            getUserMedia.call(navigator, constraints, resolve, reject);
          });
        };
      }

      const constraints = {
        video: {
          facingMode: { exact: this.facingMode }, //强制摄像头模式
          frameRate: { ideal: 15, max: 30 }, //帧率
          width: { ideal: 640, max: 1280 }, //预览宽度
          height: { ideal: 480, max: 720 }, //预览高度
        },
        audio: true,
      };
      const successFn = (stream) => {
        console.log("用户授权了");
        this.currentStream = stream;
        if ("srcObject" in this.video1Dom) {
          this.video1Dom.srcObject = stream;
        } else {
          this.video1Dom.src = window.URL.createObjectURL(stream);
        }
        console.log("开启摄像头");
        this.video1Dom.onloadedmetadata = playVideo;
        this.video1Dom.onloadeddata = playVideo;
      };
      const errorFn = (err) => {
        this.loading = false;
        console.log(err);
        alert("摄像权限授权失败，将退出实名补登记流程，请重新进入！");
        console.log("用户取消了");
        this.playErrorFn();
      };

      //ios hack方法 需要重复调用授权才可播放
      if (isIos) {
        try {
          console.log("ios客户端");
          const tapStream = await navigator.mediaDevices.getUserMedia(constraints);
          console.log("ios=tapStream==>", tapStream);
          const tapVideoTracks = tapStream?.getVideoTracks()?.[0] ?? null;
          console.log("ios=tapVideoTracks==>", tapVideoTracks);
          // 第一次不延迟关闭摄像头还是没办法播放视频
          await sleep(1000);
          tapVideoTracks?.stop?.();
          console.log("ios=stop");
          // 保险起见再延迟100ms
          await sleep(100);
        } catch (error) {
          errorFn(error);
        }
      }

      const deviceTypeArr = await navigator.mediaDevices.enumerateDevices();
      console.log("deviceTypeArr==>", deviceTypeArr);

      await sleep(0);
      this.video1Dom = this.$refs.inlineVideo;
      if (this.currentStream) this.stopMediaTracks();

      const playVideo = async (evt) => {
        if (evt) {
          this.video1Dom.onloadeddata = null;
          this.video1Dom.onloadedmetadata = null;
        }
        await this.$nextTick();
        this.video1Dom.click();
        this.video1Dom.play();

        this.video1Dom.onclick = () => {
          this.video1Dom.play().catch(() => {
            this.video1Dom.play();
          });
        };
        if (isIos) {
          // ios的微信浏览器必须先手动触发一次play，loadeddata事件才能执行
          sleep(1000).then(() => {
            playVideo();
          });
        }
        this.loading = false;
        this.playSuccessFn();
      };

      navigator.mediaDevices
        .getUserMedia(constraints)
        .then((stream) => {
          successFn(stream);
        })
        .catch((error) => {
          errorFn(error);
        });
    },
    //停止预览视频
    stopMediaTracks() {
      console.log("停止媒体");
      try {
        this.currentStream.getTracks().forEach((track) => {
          track.stop();
        });
      } catch (error) {
        console.log(error);
      }
    },
    //开始录制
    startRecording() {
      return new Promise((reslove, reject) => {
        try {
          if (this.loading) return reject("摄像头还未加载成功");
          console.log("开始录像");
          this.recorderFrame = RecordRTC(this.currentStream, {
            type: "video",
            mimeType: "video/webm;codecs=vp8,opus",
          });
          this.recorderFrame.startRecording();
          //记录开始时间
          this.startRecordTime = Date.now();
          // this.recorderFrame.camera = this.currentStream;
          reslove("OK");
        } catch (error) {
          reject("开始录像失败");
          console.error(error);
        }
      });
    },
    //停止录制并返回blob
    //如果传入了filename则返回file对象 反之base64
    stopAndblobDownload(fileName) {
      console.log("停止录像");
      return new Promise((reslove, reject) => {
        if (!this.recorderFrame) {
          return reject("当前无录像进程，无需停止");
        }
        try {
          const readBlobAsDataURL = (blob, callback) => {
            let a = new FileReader();
            a.onload = (e) => {
              callback(e.target.result);
            };
            a.readAsDataURL(blob);
          };
          this.recorderFrame.stopRecording(async () => {
            const durationTime = Date.now() - this.startRecordTime;
            // const blobObj = this.recorderFrame.getBlob();
            const blobObj = await fixWebDurationFn(
              this.recorderFrame.getBlob(),
              durationTime
            );
            //bloburl
            const blobURL = window.URL.createObjectURL(blobObj);
            console.log(blobURL);
            //文件名
            let fileNames = fileName
              ? fileName
              : "recordingVideo_" + new Date().getTime();
            //转files
            let files = new window.File([blobObj], fileNames + ".mp4", {
              type: "video/mp4",
            });
            console.log(files);
            //获取视频信息
            try {
              verifyVideo(files);
            } catch (error) {
              console.log(error);
            }
            //销毁录播流
            this.recorderFrame.destroy();
            this.recorderFrame = null;
            if (fileName) {
              //file对象
              reslove(files);
            } else {
              //base64
              readBlobAsDataURL(files, (dataurl) => {
                reslove(dataurl);
              });
            }
          });
        } catch (error) {
          reject("停止录像失败");
          console.error(error);
        }
      });
    },
    //抓图
    cathcPic() {
      return new Promise((reslove, reject) => {
        try {
          const canvas = document.createElement("canvas");
          canvas.width = this.video1Dom.videoWidth;
          canvas.height = this.video1Dom.videoHeight;
          canvas
            .getContext("2d")
            .drawImage(this.video1Dom, 0, 0, canvas.width, canvas.height);
          const src = canvas.toDataURL("image/png");
          reslove(src);
        } catch (error) {
          reject("抓图失败");
          console.error(error);
        }
      });
    },
  },
};
</script>
<style>
#recorder {
  position: relative;
  z-index: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: rgb(79, 78, 78);
}
#recorder video {
  width: 99%;
  height: 99%;
  display: block;
  position: absolute;
  left: 50%;
  top: 50%;
  object-fit: cover;
}
#recorder .maskBox {
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;
  z-index: 1;
  background-size: 100% 100%;
  background-repeat: no-repeat;
}
#recorder .loading {
  position: absolute;
  color: #fff;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
}
</style>
